﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Text;

using Microsoft.Practices.Unity;

namespace Derbster.Domain
{
    public class Match
    {
        private readonly CommandManager _commandManager;

        [ContractVerification(false)]
        internal Match()
        {
            
        }

        public Match(bool isRegulation, bool isSanctioned, Roster firstTeamRoster, Roster secondTeamRoster, string name)
            : this(isRegulation, isSanctioned, firstTeamRoster, secondTeamRoster, name, null, null)
        {
            Contract.Requires(firstTeamRoster != null);
            Contract.Requires(secondTeamRoster != null);
            Contract.Requires((isSanctioned && isRegulation) || (!isSanctioned));
            Contract.Requires(!String.IsNullOrWhiteSpace(name));
            Contract.Ensures(Jams != null);
            Contract.Ensures(Penalties != null);
            Contract.Ensures(BoxEvents != null);
        }

        public Match(bool isRegulation, bool isSanctioned, Roster firstTeamRoster, Roster secondTeamRoster, string name, string location)
            : this(isRegulation, isSanctioned, firstTeamRoster, secondTeamRoster, name, location, null)
        {
            Contract.Requires(firstTeamRoster != null);
            Contract.Requires(secondTeamRoster != null);
            Contract.Requires((isSanctioned && isRegulation) || (!isSanctioned));
            Contract.Requires(!String.IsNullOrWhiteSpace(name));
            Contract.Requires(!String.IsNullOrWhiteSpace(location));
            Contract.Ensures(Jams != null);
            Contract.Ensures(Penalties != null);
            Contract.Ensures(BoxEvents != null);
        }

        public Match(bool isRegulation, bool isSanctioned, Roster firstTeamRoster, Roster secondTeamRoster, string name, string location, DateTime? when)
        {
            Contract.Requires(firstTeamRoster != null);
            Contract.Requires(secondTeamRoster != null);
            Contract.Requires((isSanctioned && isRegulation) || (!isSanctioned));
            Contract.Requires(!String.IsNullOrWhiteSpace(name));
            Contract.Ensures(Jams != null);
            Contract.Ensures(Penalties != null);
            Contract.Ensures(BoxEvents != null);

            CurrentJamNumber = 1;
            CurrentPeriod = 1;
            GlobalIdentifier = Guid.NewGuid();
            HasStarted = false;
            HasFinished = false;
            IsRegulation = isRegulation;
            IsSanctioned = isSanctioned;
            FirstTeamRoster = firstTeamRoster;
            SecondTeamRoster = secondTeamRoster;
            Name = name;
            Location = location;
            When = when;

            Jams = new HashSet<Jam>();
            Penalties = new HashSet<Penalty>();
            BoxEvents = new HashSet<BoxEvent>();

            _commandManager = new CommandManager();
        }

        [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int MatchId { get; internal set; }
        public Guid GlobalIdentifier { get; set; }
        public bool IsPublished { get; internal set; }
        public bool IsRegulation { get; internal set; }
        public bool IsSanctioned { get; internal set; }
        public string Name { get; internal set; }
        public DateTime? When { get; internal set; }
        public string Location { get; internal set; }
        public Roster FirstTeamRoster { get; internal set; }
        public Roster SecondTeamRoster { get; internal set; }
        public OfficialRoster OfficialRoster { get; internal set; }
        
        public int CurrentJamNumber { get; internal set; }
        public int CurrentPeriod { get; internal set; }
        public bool HasStarted { get; internal set; }
        public bool HasFinished { get; internal set; }
        public bool JamInProgress { get; internal set; }
        public bool PeriodInProgress { get; internal set; }

        public HashSet<Jam> Jams { get; internal set; }
        public HashSet<Penalty> Penalties { get; internal set; }
        public HashSet<BoxEvent> BoxEvents { get; internal set; }


        [NotMapped]
        public Jam CurrentJam
        {
            get
            {
                Contract.Requires(Jams != null);
                return Jams.SingleOrDefault(j => j.Period == CurrentPeriod && j.Number == CurrentJamNumber);
            }
        }

        [NotMapped]
        public int FirstTeamTotalScore
        {
            get
            {
                Contract.Requires(Jams != null);
                return Jams.Sum(j => j.FirstTeamScoring.Sum(sp => sp.Score));
            }
        }

        [NotMapped]
        public int SecondTeamTotalScore
        {
            get
            {
                Contract.Requires(Jams != null);
                return Jams.Sum(j => j.SecondTeamScoring.Sum(sp => sp.Score));
            }
        }

        public void BeginMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(!HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Ensures(HasStarted == true);

            DoBeginMatch();

            if (_commandManager != null)
            {
                _commandManager.Add(
                    ()=>this.DoUndoBeginMatch(),
                    ()=>this.DoBeginMatch());
            }
        }

        private void DoBeginMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(!HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(Jams != null);
            Contract.Requires(CurrentJamNumber > 0);
            Contract.Requires(CurrentPeriod == 1);
            Contract.Ensures(HasStarted == true);

            HasStarted = true;
            Jams.Add(new Jam(this, CurrentPeriod, CurrentJamNumber, 1));
        }
        
        private void DoUndoBeginMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(HasStarted == false);

            HasStarted = false;
            Jams.Clear();
        }

        public void PublishMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(!HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(!IsPublished);
            Contract.Requires(CurrentPeriod == 1);
            Contract.Requires(CurrentJamNumber == 1);
            Contract.Ensures(IsPublished);

            DoPublishMatch();

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.DoUndoPublishMatch(),
                    () => this.DoPublishMatch());
            }
        }

        private void DoPublishMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(!HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(!IsPublished);
            Contract.Requires(CurrentPeriod == 1);
            Contract.Requires(CurrentJamNumber == 1);
            Contract.Ensures(IsPublished);

            IsPublished = true;
        }

        private void DoUndoPublishMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(!HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(IsPublished);
            Contract.Requires(CurrentPeriod == 1);
            Contract.Requires(CurrentJamNumber == 1);
            Contract.Ensures(!IsPublished);

            IsPublished = false;
        }

        public void EndMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Ensures(HasFinished == true);

            DoEndMatch();

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoEndMatch(),
                    () => DoEndMatch());
            }
        }

        private void DoEndMatch()
        {
            Contract.Requires(Jams != null);
            Contract.Requires(!JamInProgress);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Ensures(HasFinished == true);

            HasFinished = true;
        }

        private void DoUndoEndMatch()
        {
            Contract.Requires(!JamInProgress);
            Contract.Requires(HasStarted);
            Contract.Requires(HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(Jams != null);
            Contract.Requires(CurrentJamNumber > 0);
            Contract.Ensures(!HasFinished);

            HasFinished = false;
        }

        public void BeginPeriod()
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Ensures(PeriodInProgress == true);
            Contract.Ensures(CurrentJamNumber == 1);
            Contract.Ensures(Jams.Count >= 1);

            DoBeginPeriod();

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoBeginPeriod(),
                    () => DoBeginPeriod());
            }
        }

        private void DoBeginPeriod()
        {
            Contract.Requires(CurrentJamNumber == 1);
            Contract.Requires(Jams != null);
            Contract.Requires(Jams.Count >= 1);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Ensures(PeriodInProgress == true);
            Contract.Ensures(CurrentJamNumber == 1);
            Contract.Ensures(Jams.Count >= 1);

            this.PeriodInProgress = true;
        }

        private void DoUndoBeginPeriod()
        {
            Contract.Requires(CurrentJamNumber == 1);
            Contract.Requires(Jams != null);
            Contract.Requires(Jams.Count >= 1);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Ensures(PeriodInProgress == false);
            Contract.Ensures(CurrentJamNumber == 1);
            Contract.Ensures(Jams.Count >= 1);

            this.PeriodInProgress = false;
        }

        public void EndPeriod()
        {
            Contract.Requires(Jams != null);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(CurrentJam != null);
            Contract.Ensures(PeriodInProgress == false);

            var lastJamNumber = CurrentJamNumber;
            DoEndPeriod();

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoUndoEndPeriod(lastJamNumber),
                        () => DoEndPeriod()
                    );
            }
        }

        private void DoEndPeriod()
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(PeriodInProgress == false);

            Jams.RemoveWhere(j => j.Period == CurrentPeriod && j.Number == CurrentJamNumber);

            // If this is the first period, then advance the period and reset the jam number to 
            // one. Then add a new jam for the start of the second period.
            if (CurrentPeriod == 1)
            {
                CurrentJamNumber = 1;
                CurrentPeriod++;
                Jams.Add(new Jam(this, CurrentPeriod, CurrentJamNumber, Jams.Count + 1));
            }
            else
            {
                // The last jam never happened, so set the current jam number back one. 
                CurrentJamNumber--;
            }
           
            PeriodInProgress = false;
        }

        private void DoUndoEndPeriod(int lastJamNumber)
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(!PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Requires(CurrentJam != null);
            Contract.Ensures(CurrentJamNumber == lastJamNumber);
            Contract.Ensures(PeriodInProgress);

            CurrentJamNumber = lastJamNumber;
            CurrentPeriod--;
            PeriodInProgress = true;
        }

        public void BeginJam()
        {
            Contract.Requires(CurrentJamNumber >= 0);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == true);

            DoBeginJam();

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoUndoBeginJam(),
                        () => DoBeginJam()
                        );
            }
        }

        private void DoBeginJam()
        {
            Contract.Requires(CurrentJamNumber >= 0);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == true);

            JamInProgress = true;
        }

        private void DoUndoBeginJam()
        {
            Contract.Requires(CurrentJamNumber >= 0);
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == false);

            JamInProgress = false;
        }

        public void EndJam()
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == false);

            DoEndJam();
            
            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => this.DoUndoEndJam(),
                        () => this.DoEndJam()
                    );
            }
        }
        private void DoEndJam()
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(CurrentPeriod == 1 || CurrentPeriod == 2);
            Contract.Requires(JamInProgress);
            Contract.Requires(CurrentJamNumber >= 1);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == false);

            JamInProgress = false;
            CurrentJamNumber++;
            var rank = Jams.Count+1;
            Jams.Add(new Jam(this, CurrentPeriod, CurrentJamNumber, rank));
        }

        private void DoUndoEndJam()
        {
            Contract.Requires(HasStarted);
            Contract.Requires(!HasFinished);
            Contract.Requires(PeriodInProgress);
            Contract.Requires(!JamInProgress);
            Contract.Requires(Jams != null);
            Contract.Ensures(JamInProgress == true);

            Jams.RemoveWhere(j => j.Rank == Jams.Count);
            JamInProgress = true;
            CurrentJamNumber--;
        }

        public Penalty SkaterPenalty(Player player, string code)
        {
            Contract.Requires(player != null);
            Contract.Requires(CurrentPeriod == 1 || CurrentPeriod == 2);
            Contract.Requires(!String.IsNullOrWhiteSpace(code));
            Contract.Requires(Penalty.Codes.Contains(code));
            Contract.Requires(Jams != null);
            Contract.Requires(CurrentJam != null);
            Contract.Requires(CurrentJam.FirstTeamLineup != null);
            Contract.Requires(CurrentJam.SecondTeamLineup != null);
            Contract.Requires(CurrentJam.FirstTeamLineup.Contains(player) 
                || CurrentJam.SecondTeamLineup.Contains(player));
            Contract.Requires(CurrentJamNumber >= 1); 
            Contract.Requires(Penalties != null);
            

            var penalty = DoSkaterPenalty(player, code, CurrentPeriod, CurrentJamNumber);
           
            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => this.DoUndoSkaterPenalty(player,code,CurrentPeriod,CurrentJamNumber), 
                        () => this.DoSkaterPenalty(player,code,CurrentPeriod,CurrentJamNumber)
                    );
            }

            return penalty;
        }

        private Penalty DoSkaterPenalty(Player player, string code, int period, int jamNumber)
        {
            Contract.Requires(player != null);
            Contract.Requires(!string.IsNullOrWhiteSpace(code));
            Contract.Requires(Penalty.Codes.Contains(code));
            Contract.Requires(period == 1 || period == 2);
            Contract.Requires(jamNumber >= 1);
            Contract.Requires(Penalties != null);
            
            int rank = Penalties.Count(p=>p.Player == player)+1;
            var penalty = new Penalty(this, player, code, period, jamNumber, rank);
            Penalties.Add(penalty);

            if (player.State == PlayerState.Ready)
                player.State = PlayerState.InQueue;
            return penalty;
        }

        private void DoUndoSkaterPenalty(Player player, string code, int period, int jamNumber)
        {
            Contract.Requires(player != null);
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Count > 0);
            Contract.Requires(period == 1 || period == 2);
            Contract.Requires(Penalties.Any(p => p.Player == player 
                && p.Period == period
                && p.JamNumber == jamNumber 
                && p.Code == code));

            var lastPenalty =
                Penalties.Where(p => 
                    p.Player == player && 
                    p.Period == period &&
                    p.JamNumber == jamNumber && 
                    p.Code == code).OrderBy(p=>p.Rank).Last();

            Penalties.Remove(lastPenalty);

            if (player.State == PlayerState.InQueue)
                player.State = PlayerState.Ready;
        }

        public string CorrectPenalty(Player player, int rank, string code)
        {
            Contract.Requires(player != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(code));
            Contract.Requires(Penalty.Codes.Contains(code));
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Count > 0);
            Contract.Requires(Penalties.Any(s => s.Player == player 
                && s.Rank == rank));

            var oldCode = DoCorrectPenalty(player, rank, code);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoCorrectPenalty(player, rank, oldCode),
                        () => DoCorrectPenalty(player, rank, code)
                    );
            }

            return oldCode;
        }

        private string DoCorrectPenalty(Player player, int rank, string code)
        {
            Contract.Requires(player != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(code));
            Contract.Requires(Penalty.Codes.Contains(code));
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Count > 0);
            Contract.Requires(Penalties.Any(s => s.Player == player
                && s.Rank == rank));
            var penalty =
                Penalties.Single(s => s.Player == player 
                    && s.Rank == rank);

            string oldCode = penalty.Code;
            penalty.Code = code;
            return oldCode;
        }

        public string CorrectLastPenalty(Player player, string newCode)
        {
            Contract.Requires(player != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(newCode));
            Contract.Requires(Penalty.Codes.Contains(newCode));
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Any(p => p.Player == player));

            var oldCode = DoCorrectLastPenalty(player, newCode);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoCorrectLastPenalty(player, oldCode),
                        () => DoCorrectLastPenalty(player, newCode)
                    );
            }

            return oldCode;
        }

        private string DoCorrectLastPenalty(Player player, string newCode)
        {
            Contract.Requires(player != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(newCode));
            Contract.Requires(Penalty.Codes.Contains(newCode));
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Any(p => p.Player == player));

            var lastPenalty = Penalties.Where(p => p.Player == player).OrderBy(p => p.Rank).Last();
            string oldCode = lastPenalty.Code;
            lastPenalty.Code = newCode;
            return oldCode;
        }

        public Penalty RemoveLastPenalty(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Any(p => p.Player == player));

            var lastPenalty = DoRemoveLastPenalty(player);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoSkaterPenalty(player, lastPenalty.Code, lastPenalty.Period, lastPenalty.JamNumber),
                        () => DoRemoveLastPenalty(player)
                    );
            }

            return lastPenalty;
        }

        private Penalty DoRemoveLastPenalty(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Penalties != null);
            Contract.Requires(Penalties.Any(p => p.Player == player));
            var lastPenalty = Penalties.Where(p => p.Player.Equals(player)).OrderBy(p => p.Rank).Last();
            Penalties.Remove(lastPenalty);
            if (player.State == PlayerState.InQueue) 
                player.State = PlayerState.Ready;

            return lastPenalty;
        }

        public void PlayerSeated(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(FirstTeamRoster != null);
            Contract.Requires(SecondTeamRoster != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(CurrentJam != null);
            Contract.Requires(CurrentJamNumber > 0);

            var previousState = player.State;
            DoPlayerSeated(player);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoUndoPlayerSeated(player, previousState),
                        () => DoPlayerSeated(player)
                    );
            }
        }

        private BoxEvent DoPlayerSeated(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(FirstTeamRoster != null);
            Contract.Requires(SecondTeamRoster != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(CurrentJam != null);
            Contract.Requires(CurrentJamNumber > 0);
            Contract.Requires(CurrentPeriod == 1 || CurrentPeriod == 2);

            var rank = BoxEvents.Count(be => be.Player == player)+1;
            
            var boxEvent = new BoxEvent(this,player,CurrentPeriod,CurrentJamNumber,BoxEventType.Seated,
                rank);
            BoxEvents.Add(boxEvent);
            player.State = PlayerState.InBox;
            return boxEvent;
        }

        private void DoUndoPlayerSeated(Player player, PlayerState previousState)
        {
            Contract.Requires(player != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(BoxEvents.Any(be => be.Player == player && be.BoxEventType == BoxEventType.Seated));

            var lastBoxEvent = BoxEvents.Where(be => be.Player == player && be.BoxEventType == BoxEventType.Seated)
                    .OrderBy(be => be.Rank)
                    .Last();

            BoxEvents.Remove(lastBoxEvent);
            player.State = previousState;
        }

        public void PlayerReleased(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(player.State == PlayerState.InBox);
            Contract.Requires(FirstTeamRoster != null);
            Contract.Requires(SecondTeamRoster != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(CurrentJam != null);
            Contract.Requires(CurrentJamNumber > 0);

            var previousState = player.State;
            DoPlayerReleased(player);

            if (_commandManager != null)
            {
                _commandManager.Add(
                        () => DoUndoPlayerReleased(player, previousState),
                        () => DoPlayerReleased(player)
                    );
            }
        }

        private BoxEvent DoPlayerReleased(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(player.State == PlayerState.InBox);
            Contract.Requires(FirstTeamRoster != null);
            Contract.Requires(SecondTeamRoster != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(CurrentJam != null);
            Contract.Requires(CurrentJamNumber > 0);
            Contract.Requires(CurrentPeriod == 1 || CurrentPeriod == 2);

            var rank = BoxEvents.Count(be => be.Player == player) + 1;

            var boxEvent = new BoxEvent(this, player, CurrentPeriod, CurrentJamNumber, BoxEventType.Released, 
                rank);
            BoxEvents.Add(boxEvent);
            player.State = PlayerState.Ready;

            return boxEvent;
        }

        private void DoUndoPlayerReleased(Player player, PlayerState previousState)
        {
            Contract.Requires(player != null);
            Contract.Requires(BoxEvents != null);
            Contract.Requires(BoxEvents.Any(be => be.Player == player && be.BoxEventType == BoxEventType.Released));

            var lastBoxEvent = BoxEvents.Where(be => be.Player == player && be.BoxEventType == BoxEventType.Released)
                    .OrderBy(be => be.Rank)
                    .Last();

            BoxEvents.Remove(lastBoxEvent);
            player.State = previousState;
        }


        [Pure]
        public bool VerifyMatch()
        {
            Contract.Requires(HasFinished);
            if (IsSanctioned && !IsRegulation) 
                return false;

            if (Jams.Any(jam => !jam.VerifyJam()))
            {
                return false;
            }

            if (Penalties.Any(penalty => !penalty.VerifyPenalty()))
            {
                return false;
            }

            return true;
        }

        protected bool Equals(Match other)
        {
            Contract.Requires(other != null);

            var boxEventSetComparer = HashSet<BoxEvent>.CreateSetComparer();
            var jamSetComparer = HashSet<Jam>.CreateSetComparer();
            var penaltySetComparer = HashSet<Penalty>.CreateSetComparer();

            return IsRegulation.Equals(other.IsRegulation) 
                && IsSanctioned.Equals(other.IsSanctioned) 
                && Equals(FirstTeamRoster, other.FirstTeamRoster) 
                && Equals(SecondTeamRoster, other.SecondTeamRoster) 
                && Equals(OfficialRoster, other.OfficialRoster) 
                && CurrentPeriod == other.CurrentPeriod
                && HasStarted.Equals(other.HasStarted) 
                && HasFinished.Equals(other.HasFinished) 
                && JamInProgress.Equals(other.JamInProgress) 
                && PeriodInProgress.Equals(other.PeriodInProgress) 
                && jamSetComparer.Equals(Jams, other.Jams) 
                && penaltySetComparer.Equals(Penalties, other.Penalties) 
                && boxEventSetComparer.Equals(BoxEvents, other.BoxEvents);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }
            if (ReferenceEquals(this, obj))
            {
                return true;
            }
            if (obj.GetType() != this.GetType())
            {
                return false;
            }
            return Equals((Match)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = IsRegulation.GetHashCode();
                hashCode = (hashCode * 397) ^ IsSanctioned.GetHashCode();
                hashCode = (hashCode * 397) ^ (FirstTeamRoster != null ? FirstTeamRoster.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (SecondTeamRoster != null ? SecondTeamRoster.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (OfficialRoster != null ? OfficialRoster.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ CurrentPeriod;
                hashCode = (hashCode * 397) ^ HasStarted.GetHashCode();
                hashCode = (hashCode * 397) ^ HasFinished.GetHashCode();
                hashCode = (hashCode * 397) ^ JamInProgress.GetHashCode();
                hashCode = (hashCode * 397) ^ PeriodInProgress.GetHashCode();
                hashCode = (hashCode * 397) ^ (Jams != null ? Jams.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (Penalties != null ? Penalties.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (BoxEvents != null ? BoxEvents.GetHashCode() : 0);
                return hashCode;
            }
        }
    }
}
